/*jslint browser: true, nomen: true, es5: true*/
/*global $, jQuery, window, requirejs, require, define, Promise*/
(function (factory) {
    "use strict";
	if (typeof define === "function" && define.amd) {
		// AMD. Register as an anonymous module.
		define(factory);
	} else {
        // Browser globals
        window.lhmcv = window.lhmcv || {};
		window.lhmcv.IndexedDB = factory();
	}
}(function () {
    "use strict";
    if (!window.indexedDB) {
        window.alert("您的浏览器不支持IndexDB，请使用最新的chrome浏览器！");
        throw new Error("Your browser doesn't support a stable version of IndexedDB."
                        + " Such and such feature will not be available.");
    }
    if (!window.Promise) {
        window.alert("您的浏览器不支持Promise，请使用最新的chrome浏览器！");
        throw new Error("Your browser doesn't support a stable version of Promise."
                        + " Such and such feature will not be available.");
    }
    var indexedDB_instance_collection = {},
        IndexedDB;
    
    function open(db_name, db_version) {
        return new Promise(function (resolve, reject) {
            var request,
                is_update = false,
                indexedDB_instance = indexedDB_instance_collection[db_name];
            if (indexedDB_instance) {
                reject("IndexedDB error on opening 1st: "
                       + "instance exist already.");
            }
            request = window.indexedDB.open(db_name, db_version);
            request.onupgradeneeded = function (event) {
                is_update = true;
            };
            request.onerror = function (event) {
            };
            request.onsuccess = function (event) {
                indexedDB_instance = event.target.result;
                resolve(indexedDB_instance, is_update);
            };
        });
    }
    
    function destroy(db_name) {
        return new Promise(function (resolve, reject) {
            var indexedDB_instance = indexedDB_instance_collection[db_name],
                destory_request;
            if (!indexedDB_instance) {
                reject("IndexedDB error on opening destroy: "
                       + "no suc instance.");
            }
            indexedDB_instance.close();
            destory_request = window.indexedDB.deleteDatabase(db_name);
            destory_request.onerror = function (event) {
                reject("IndexedDB error on destorying: "
                       + event.target.error.message);
            };
            destory_request.onsuccess = function (event) {
                resolve();
            };
        });
    }
    
    /*build indexed table in browser, used in function create_tables*/
    function build_table(tableName, keyPath, index_arg_list) {
        var global = window.lhmc_mvc_global,
            indexdb = global.instance.indexedDB,
            objectStore,
            index,
            length = index_arg_list.length;
        //create table
        objectStore = indexdb.createObjectStore(tableName, { keyPath: keyPath });
        //create index
        for (index = 0; index < length; index += 1) {
            //objectStore.createIndex("pk", "pk", { unique: true });
            objectStore.createIndex.apply(objectStore, index_arg_list[index]);
        }
    }
    
    function create_tables(indexdb_format_dict) {
        var all_dict = indexdb_format_dict,
            tableName,
            table_dict;
        for (tableName in all_dict) {
            if (all_dict.hasOwnProperty(tableName)) {
                table_dict = all_dict[tableName];
                build_table(tableName, table_dict.keyPath, table_dict.index_arg_list);
            }
        }
    }
    
    function init_db(db_name, db_version, indexdb_format_dict) {
        return new Promise(function (resolve, reject) {
            var indexedDB_instance = indexedDB_instance_collection[db_name],
                request;
            if (indexedDB_instance) {
                reject("IndexedDB error on opening 2st: "
                       + "instance exist already.");
            }
            request = window.indexedDB.open(db_name, db_version);
            request.onupgradeneeded = function (event) {
                indexedDB_instance = event.target.result;
                create_tables(indexdb_format_dict);
            };
            request.onerror = function (event) {
                reject("IndexedDB error on opening 2nd: " + event.target.error.message);
            };
            request.onsuccess = function (event) {
                indexedDB_instance = event.target.result;
                resolve(indexedDB_instance);
            };
        });
    }
    
    function trans_onfail(obj_name, action_name, error_message) {
        var return_list = ["IndexedDB Transaction fail",
                           action_name, obj_name, error_message];
        return return_list.join("::");
    }
    
    IndexedDB = function (name, db_name) {
        this.indexDB = indexedDB_instance_collection[db_name];
        if (!this.indexDB) {
            throw new Error("IndexedDB error: IndexedDB instance is not exist;");
        }
        this.name = name;
    };
    IndexedDB.get_instance = function (db_name) {
        var indexedDB_instance = indexedDB_instance_collection[db_name];
        if (!indexedDB_instance) {
            throw new Error("IndexedDB error: IndexedDB instance is not exist;");
        }
        return indexedDB_instance;
    };
    IndexedDB.init_db_async =  function (db_name, db_version, indexdb_format_dict) {
        return open(db_name, db_version).then(function (indexedDB_instance, is_update) {
            if (is_update) {
                return IndexedDB.destory_db_async(db_name, db_version, indexdb_format_dict);
            } else {
                return arguments;
            }
        });
    };
    IndexedDB.destory_db_async =  function (db_name, db_version, indexdb_format_dict) {
        return destroy(db_name).then(function () {
            init_db(db_name, db_version, indexdb_format_dict);
        });
    };
    IndexedDB.prototype = {
        /*
        CURD, callback： {
            trans_pass: function(){},
            trans_fail: function(){},
            trans_self: this,
            store_pass: function(){},
            store_fail: function(){},
            store_self: this
        }
        */
        create: function (data_list, filter) {
            var transaction,
                objectStore,
                request,
                index,
                length = data_list.length,
                pk_list = [],
                action_name = "create",
                obj_name = this.name;
            return new Promise(function (resolve, reject) {
                //transaction
                transaction = this.indexDB.transaction(obj_name, "readwrite");
                transaction.oncomplete = function (event) {
                    resolve(pk_list);
                };
                transaction.onerror = function (event) {
                    var error_message = event.target.error.message;
                    reject(trans_onfail(obj_name, action_name, error_message));
                };
                //objectStore
                objectStore = transaction.objectStore(obj_name);
                function add_pass(event) {
                    var pk = event.target.result;
                    if (filter) {
                        if (filter(pk)) {
                            pk_list.push(pk);
                        }
                    } else {
                        pk_list.push(pk);
                    }
                }
                /*function add_fail(event) {
                    var error_message = event.target.error.message;
                    store_onfail(obj_name, action_name, callback, error_message);
                }*/
                for (index = 0; index < length; index += 1) {
                    request = objectStore.add(data_list[index]);
                    request.onsuccess = add_pass;
                    //request.onerror = add_fail;
                }
            });
        },
        update: function (data_list, filter) {
            var transaction,
                objectStore,
                request,
                index,
                length = data_list.length,
                pk_list = [],
                action_name = "update",
                obj_name = this.name;
            return new Promise(function (resolve, reject) {
                //transaction
                transaction = this.indexDB.transaction(obj_name, "readwrite");
                transaction.oncomplete = function (event) {
                    resolve(pk_list);
                };
                transaction.onerror = function (event) {
                    var error_message = event.target.error.message;
                    reject(trans_onfail(obj_name, action_name, error_message));
                };
                //objectStore
                objectStore = transaction.objectStore(obj_name);
                function put_pass(event) {
                    // event.target.result == customerData[index].ssn
                    var pk = event.target.result;
                    if (filter) {
                        if (filter(pk)) {
                            pk_list.push(pk);
                        }
                    } else {
                        pk_list.push(pk);
                    }
                }
                /*function put_fail(event) {
                    var error_message = event.target.error.message;
                    store_onfail(obj_name, action_name, callback, error_message);
                }*/
                for (index = 0; index < length; index += 1) {
                    request = objectStore.put(data_list[index]);
                    request.onsuccess = put_pass;
                    //request.onerror = put_fail;
                }
            });
        },
        get: function (pk) {
            var transaction,
                objectStore,
                request,
                item_dict,
                action_name = "get",
                obj_name = this.name;
            return new Promise(function (resolve, reject) {
                //transaction and store
                transaction = this.indexDB.transaction(obj_name, "readonly");
                transaction.oncomplete = function (event) {
                    resolve(item_dict);
                };
                transaction.onerror = function (event) {
                    var error_message = event.target.error.message;
                    reject(trans_onfail(obj_name, action_name, error_message));
                };
                //objectStore
                objectStore = transaction.objectStore(obj_name);
                request = objectStore.get(pk);
                request.onsuccess = function (event) {
                    item_dict = event.target.result;
                };
                /*request.onerror = function (event) {
                    var error_message = event.target.errorCode;
                    store_onfail(obj_name, action_name, callback, error_message);
                };*/
            });
        },
        getAll: function (filter) {
            var transaction,
                objectStore,
                request,
                item_dict_list = [],
                action_name = "getAll",
                obj_name = this.name;
            return new Promise(function (resolve, reject) {
                //transaction
                transaction = this.indexDB.transaction(obj_name, "readonly");
                transaction.oncomplete = function (event) {
                    resolve(item_dict_list);
                };
                transaction.onerror = function (event) {
                    var error_message = event.target.error.message;
                    reject(trans_onfail(obj_name, action_name, error_message));
                };
                //objectStore
                objectStore = transaction.objectStore(obj_name);
                request = objectStore.openCursor();
                request.onsuccess = function (event) {
                    var cursor = event.target.result,
                        item_dict;
                    if (cursor) {
                        item_dict = cursor.value;
                        if (filter) {
                            if (filter(item_dict)) {
                                item_dict_list.push(item_dict);
                            }
                        } else {
                            item_dict_list.push(item_dict);
                        }
                        cursor.continue();
                    }
                };
                /*request.onerror = function (event) {
                    var error_message = event.target.errorCode;
                    store_onfail(obj_name, action_name, callback, error_message);
                };*/
            });
        },
        getLastest: function () {
            var transaction,
                objectStore,
                index,
                request,
                lastest_item_dict,
                action_name = "getLastest",
                obj_name = this.name;
            return new Promise(function (resolve, reject) {
                //transaction
                transaction = this.indexDB.transaction(obj_name, "readonly");
                transaction.oncomplete = function (event) {
                    resolve(lastest_item_dict);
                };
                transaction.onerror = function (event) {
                    var error_message = event.target.error.message;
                    reject(trans_onfail(obj_name, action_name, error_message));
                };
                //objectStore
                objectStore = transaction.objectStore(obj_name);
                index = objectStore.index('pk');
                request = index.openCursor(null, 'prev');
                request.onsuccess = function (event) {
                    var cursor = event.target.result;
                    lastest_item_dict = cursor ? cursor.value : {pk: 0};
                };
                /*request.onerror = function (event) {
                    var error_message = event.target.errorCode;
                    store_onfail(obj_name, action_name, callback, error_message);
                };*/
            });
        },
        /*indexQueryDict: {
            "indexName": index name,
            "method": upperBound/lowerBound/bound/only
            lower: []/var,
            upper: []/var,
            lowerOpen: true/false, fale <=
            upperOpen: true/false,  true >
			only: []/var}*/
        searchIndex: function (indexQueryDict, filter) {
            var transaction,
                objectStore,
                methodName = indexQueryDict.method,
                indexName = indexQueryDict.indexName,
                index,
                aRange,
                request,
                item_dict_list = [],
                action_name = "searchIndex",
                obj_name = this.name;
            return new Promise(function (resolve, reject) {
                //transaction
                transaction = this.indexDB.transaction(obj_name, "readonly");
                transaction.oncomplete = function (event) {
                    resolve(item_dict_list);
                };
                transaction.onerror = function (event) {
                    var error_message = event.target.error.message;
                    reject(trans_onfail(obj_name, action_name, error_message));
                };
                //objectStore
                objectStore = transaction.objectStore(obj_name);
                index = objectStore.index(indexName);
                switch (methodName) {
                case "upperBound":
                    aRange = window.IDBKeyRange.upperBound(
                        indexQueryDict.upper,
                        indexQueryDict.upperOpen
                    );
                    break;
                case "lowerBound":
                    aRange = window.IDBKeyRange.lowerBound(
                        indexQueryDict.lower,
                        indexQueryDict.lowerOpen
                    );
                    break;
                case "bound":
                    aRange = window.IDBKeyRange.bound(
                        indexQueryDict.lower,
                        indexQueryDict.upper,
                        indexQueryDict.lowerOpen,
                        indexQueryDict.upperOpen
                    );
                    break;
                case "only":
                    aRange = window.IDBKeyRange.only(indexQueryDict.only);
                    break;
                }
                request = index.openCursor(aRange);
                request.onsuccess = function (event) {
                    var cursor = event.target.result,
                        item_dict;
                    if (cursor) {
                        item_dict = cursor.value;
                        item_dict_list.push(item_dict);
                        cursor.continue();
                    }
                };
                /*request.onerror = function (event) {
                    var error_message = event.target.errorCode;
                    store_onfail(obj_name, action_name, callback, error_message);
                };*/
            });
        },
        delete: function (pk, callback) {
            var transaction,
                objectStore,
                request,
                return_data = false,
                obj_name = this.name,
                action_name = "delete";
            return new Promise(function (resolve, reject) {
                //transaction and store
                transaction = this.indexDB.transaction(obj_name, "readwrite");
                transaction.oncomplete = function (event) {
                    resolve(return_data);
                };
                transaction.onerror = function (event) {
                    var error_message = event.target.error.message;
                    reject(trans_onfail(obj_name, action_name, error_message));
                };
                //objectStore
                objectStore = transaction.objectStore(obj_name);
                request = objectStore.delete(pk);
                request.onsuccess = function (event) {
                    return_data = true;
                };
                /*request.onerror = function (event) {
                    var error_message = event.target.errorCode;
                    return_data = false;
                    store_onfail(obj_name, action_name, callback, error_message);
                };*/
            });
        }
    };
    return IndexedDB;
}));